package com.anxpp.calculator.nio;

import com.anxpp.utils.Calculator;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO服务端
 * @author lj
 * @version 1.0
 */
public class ServerHandle implements Runnable{
    private Selector selector;
    private ServerSocketChannel serverChannel;
    private volatile boolean started;

    /**
     * 构造方法
     * @param port 指定要监听的端口号
     */
    public ServerHandle(int port){
        try{
            //创建选择器
            selector = Selector.open();
            //打开监听通道
            serverChannel = ServerSocketChannel.open();
            //true，此通道被置为阻塞模式；false，此通道被置为非阻塞模式
            serverChannel.configureBlocking(false);//开启非阻塞模式
//            serverChannel.configureBlocking(true);//开启非阻塞模式
            //绑定端口backlog设置为1024
            serverChannel.socket().bind(new InetSocketAddress(port), 1024);
            //监听客户端连接请求;把通道注册到多路复用器上，并监听阻塞事件
            /**
             * SelectionKey.OP_READ   : 表示关注－－读数据就绪事件
             * SelectionKey.OP_WRITE  : 表示关注－－写数据就绪事件
             * SelectionKey.OP_CONNECT: 表示关注－－socket channel的连接完成事件
             * SelectionKey.OP_ACCEPT : 表示关注－－server-socket channel的accept事件
             */
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            //标记服务器开启
            started = true;
            System.out.println("服务已启动，端口号：" + port);
        }catch(IOException e){
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop(){
        started = false;
    }

    //需要一个线程负责Selector的轮询
    public void run(){
        //循环遍历selector
        while(started){
            try{
                /**
                 * a.select() 阻塞到至少有一个通道在你注册的事件上就绪
                 * b.select(long timeOut) 阻塞到至少有一个通道在你注册的事件上就绪或者超时timeOut
                 * c.selectNow() 立即返回。如果没有就绪的通道则返回0
                 * select方法的返回值表示就绪通道的个数。
                 */
                //无论是否有读写事件发生，selector每隔1s被唤醒一次
//                selector.select(1000);
                //阻塞，只有当至少一个注册的事件发生的时候才会继续
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key = null;
                while(it.hasNext()){
                    key = it.next();
                    it.remove();
//                    handleInput(key);
                    try{
                        handleInput(key);
                    }catch(Exception e) {
                        System.out.println("error happen!"+e.getMessage());
                        if(key != null){
                            key.cancel();
                            if (key.channel() != null){
                                key.channel().close();
                            }
                        }
                    }
                }
            }catch(Throwable t){
                t.printStackTrace();
            }
        }

        //selector关闭后会自动释放里面管理的资源
        if(selector != null) {
            try {
                selector.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void handleInput(SelectionKey key) throws IOException{
        if(key.isValid()){
            //处理新接入的请求消息
            if(key.isAcceptable()){
                // 1.获取通道服务
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                //通过ServerSocketChannel的accept创建SocketChannel实例
                //完成该操作意味着完成TCP三次握手，TCP物理链路正式建立
                // 2.执行阻塞方法
                SocketChannel sc = ssc.accept();
                // 3.设置服务器通道为非阻塞模式，true为阻塞，false为非阻塞
                sc.configureBlocking(false);
                // 4.把通道注册到多路复用器上，并设置读取标识
                sc.register(selector, SelectionKey.OP_READ);
            }
            //读消息
            if(key.isReadable()){
                //创建ByteBuffer，并开辟一个1M的缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(1024);
//                ByteBuffer buffer = (ByteBuffer)key.attachment();//此句会导致报错，导致前面channel关闭
                // 1.清空缓冲区数据
                buffer.clear();
                // 2.获取在多路复用器上注册的通道
                SocketChannel sc = (SocketChannel) key.channel();
                // 3.读取请求码流，返回读取到的字节数
                long readBytes = sc.read(buffer);
                //读取到字节，对字节进行编码
                // 4.返回内容为-1 表示没有数据
                if(readBytes>0){
                    // 5.将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
                    buffer.flip();
                    // 6.根据缓冲区可读字节数创建字节数组
                    byte [] bytes = new byte[buffer.remaining()];
                    // 7.将缓冲区可读字节数组复制到新建的数组中
                    buffer.get(bytes);
                    String expression = new String(bytes, "UTF-8");
                    //处理数据
                    String result = null;
                    try{
                        result = Calculator.conversion(expression.replace("\n","")).toString();
                    }catch(Exception e){
                        result = "计算错误：" + e.getMessage();
                    }
                    System.out.println("服务器收到消息："+expression+",将要返回的计算结果："+result);
                    //发送应答消息
                    doWrite(sc, result);
                }
                //没有读取到字节，忽略
//                else if(readBytes == 0);
                //链路已关闭，释放资源
                else if (readBytes < 0){
                    key.cancel();
                    sc.close();
                }
            }
        }
    }

    //异步发送应答消息
    private void doWrite(SocketChannel channel, String response) throws IOException {
        //将消息编码为字节数组
        byte[] bytes = response.getBytes();
        //根据数组容量创建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //将字节数组复制到缓冲区
        writeBuffer.put(bytes);
        //flip操作
        writeBuffer.flip();
//        writeBuffer.compact();//一样，服务器还是会收到多个请求合在一起的数据
        //发送缓冲区的字节数组
//        channel.write(writeBuffer);
        //此处不包含处理"写半包"的代码
        while(writeBuffer.hasRemaining()){
            channel.write(writeBuffer);
        }
//        writeBuffer.compact();
//        if (!writeBuffer.hasRemaining()){
//            //如果缓冲区中的数据已经全部写入了信道，则将该信道感兴趣的操作设置为可读
////            System.out.println("---Send order 2 server succeed.");
//            ;
//        }
//        else {
//            System.out.println("---still has remaining---.");
//            channel.write(writeBuffer);
//        }
//        if (writeBuffer.hasRemaining()){
//            channel.write(writeBuffer);
//        }
    }




}

